<?php
require_once 'models/acl.inc';
require_once 'models/catalog.inc';
require_once 'models/cms.inc';
require_once 'models/productpage.inc';
require_once 'models/glossary_item_page.inc';
require_once 'models/meta.inc';
require_once 'util/translit.inc';
require_once 'common/storage.inc';
require_once 'models/orm.inc';


class TMSTypeAggregateProperty extends TProperty {
	/**
	 * @var IItem
	 */
	private $_item_;

	public function __construct(IPropertyMeta $meta, TMSTypeAggregateItem $item){
		parent::__construct($meta);
		$this->_item_ = $item;
	}

	/**
	 * gets property value
	 * @return mixed
	 */
	public function Value(){
		$nm = $this->Name();
		return $this->_item_->$nm;
	}
	/**
	 * gets property item
	 * @return IItem
	 */
	public function Item(){
		return $this->_item_;
	}
}

abstract class TMSTypeAggregateItem implements IItem {
	private $_name_;
	
	protected $customType;
	
	protected $properties;
	
	protected $meta;
	
	/**
	 * @var TMSTypesModel
	 */
	protected $model;
	
	public function __construct(TMSType $type,$nm, $meta, TMSTypesModel $model){
		$this->customType = $type;
		$this->_name_ = $nm;
		$this->meta = $meta;
		$this->model = $model;
	}
	
	protected abstract function checkProperty(IPropertyMeta $p);
	
	protected abstract function getClassProperties();
	
	protected function checkProperties(){
		if (empty($this->properties)){
			$this->properties = array();
			$cp = $this->getClassProperties();
			foreach ($cp as $p)
				if ($this->checkProperty($p))
				$this->properties[$p->Name()] = new TMSTypeAggregateProperty($p,$this);
		}
	}
	
	public function __get($nm){
		if ($nm == 'ClassName')
			return $this->customType->ClassName;
		if (isset($this->meta->$nm))
			return $this->meta->$nm;
		return null;
	}
	
	public function ItemId(){
		return $this->customType->ClassName.'.'.$this->_name_;
	}
	
	/**
	 * gets string representation of item
	 * @return string
	 */
	public function __toString(){
		return (string)$this->Caption;
	}
	/**
	 * gets item properties
	 * @param string[] $includetypes array of property types, which properties should be included
	 * @param string[] $excludetypes array of property types, which properties should be excluded
	 * @return IProperty[]
	 * @see TItemPropertyType
	 * @see IProperty
	 */
	public function Properties($includetypes = array(),$excludetypes = array()){
		$this->checkProperties();
		return $this->properties;
	}
	/**
	 * gets item property
	 * @param string $name property name
	 * @return IProperty
	 * @see IProperty
	 */
	public function Property($name){
		$this->checkProperties();
		if (isset($this->properties[$name]))
			return $this->properties[$name];
		return null;
	}
}

class TMSTypeAttributeItem extends TMSTypeAggregateItem {
	protected function getClassProperties(){
		return $this->model->AttributeProperties();
	}
	
	protected function checkProperty(IPropertyMeta $p){
		return $this->model->CheckAttributeProperty($this,$p);		
	}
	
	public static function GetClassName(){
		return 'TMSTypeAttribute';		
	}

	/**
	 * gets item class name
	 * @return string
	 */
	public function ClassName(){
		return self::GetClassName();
	}
}

class TMSTypeOrderOptionItem extends TMSTypeAggregateItem {
	protected function getClassProperties(){
		return $this->model->OptionProperties();
	}

	protected function checkProperty(IPropertyMeta $p){
		return true;
	}

	public static function GetClassName(){
		return 'TMSTypeOrderOption';
	}

	/**
	 * gets item class name
	 * @return string
	 */
	public function ClassName(){
		return self::GetClassName();
	}
}

class TMSTypeItem extends TCandyItem {
	public function __get($nm){
		switch ($nm){
			case 'Attributes':{
				$result = array();
				foreach ($this->AttrMeta as $nm => $meta)
					$result[] = new TMSTypeAttributeItem($this->Base, $nm, $meta, $this->Model);
				return $result;
			}break;
			case 'OrderOptions':{
				$result = array();
				foreach ($this->Base->OrderOptions as $nm => $meta)
					$result[] = new TMSTypeOrderOptionItem($this->Base, $nm, $meta, $this->Model);
				return $result;
			}break;
			default:return parent::__get($nm);break;
		}
	}
	
	public function ItemId(){
		return $this->ClassName;
	}
}


class TMSTypesModel extends TCandyModel implements IKeyValueRepository, IClassLoader {
	
/**
 * @var ITransliter
 */	
	protected $_ioc_transliter_;
	
/**
 * @var IDBDriver
 */	
	protected $_ioc_driver_;
	
	protected $_class_def_path_;

	private $_types_cache_ = array();
	
	private $_ad_cache_ = array();
	
	private $_forced_cache_ = array();
	
	private $_attr_properties_;
	
	private $_opt_properties_;
	
	private $_selection_cache_ = array();
	
	public function GetItem($id){
		if ((!class_exists($id) || is_subclass_of($id, 'TNOrmObject')) 
			&& $id != 'TMSNomenclature'
			&& $id != 'TMSGoods'
			&& $id != 'TMSService'
		){
			if (!isset($this->_types_cache_[$id])){
				$type = parent::GetItem($id);
				$this->_types_cache_[$id] = $type?$type:false;
			}
			if ($this->_types_cache_[$id] === false)
				return null;
			return $this->_types_cache_[$id];
		}
		return null;
	}
	
	protected function afterConstruct(){
		$this->_conf_classname_ = 'TMSType';
		$this->_array_id_properties_ = array('ClassName');
		$this->ClassDefinitionPath = 'private/models/custom';
	}
	
	public function __set($nm, $value){
		switch ($nm){
			case 'ClassDefinitionPath':$this->_class_def_path_ = $this->Application()->AdjustPath($value);break;
			default:parent::__set($nm,$value);break;
		}
	}
	
	public function __get($nm){
		switch ($nm){
			case 'ClassDefinitionPath':return $this->_class_def_path_;break;
			case 'OrmSettings':{
				if ($result = parent::__get($nm))
					return $result;
				return $this;
			}break;
			default:return parent::__get($nm);break;
		}
	}

	public function LoadClass($classname){
		if (!file_exists($this->ClassDefinitionPath.'/'.$classname.'.inc') && ($type = $this->GetItem($classname))){
			$def = $type->ClassDefinition;
			TFileSystem::ForceDir($this->ClassDefinitionPath);
			file_put_contents($this->ClassDefinitionPath.'/'.$classname.'.inc', $def);
		}
		if (file_exists($this->ClassDefinitionPath.'/'.$classname.'.inc'))
			include_once $this->ClassDefinitionPath.'/'.$classname.'.inc';
		return class_exists($classname,false);
	}
	
	protected function postProcessProperties(){
		parent::postProcessProperties();
		unset($this->propertyMetas['ClassName']);
		$this->propertyMetas['BaseClass']->SetSelection(array(
					TMSType::BASE_NONE => 'none',
					TMSType::BASE_GOODS => 'Goods',
					TMSType::BASE_SERVICE => 'Service',
					TMSTYpe::BASE_NOMENCLATURE=>'Nomenclature',
					TMSType::BASE_CLIENT => 'Client'
				));
		//unset($this->propertyMetas['Base']);
		//unset($this->propertyMetas['Meta']);
		unset($this->propertyMetas['ClassDefinition']);
		$this->propertyMetas['Attributes'] = new TOrmCollectionPropertyMeta('Attributes', 'Attributes', 'TMSTypeAttribute');
		$this->propertyMetas['OrderOptions'] = new TOrmCollectionPropertyMeta('OrderOptions', 'Order options', 'TMSTypeOrderOption');
		$arts = $this->GetList(new TListOptions(array(new TFilterOption('BaseClass', TConditionType::C_IN, array(TMSType::BASE_GOODS, TMSType::BASE_SERVICE)))));
		$s = array(
				'TMSGoods'=>'Goods',
				'TMSService'=>'Service'
				);
		foreach ($arts as $a)
			$s[$a->ClassName] = $a->Name;
		$this->propertyMetas['ArticleClass']->SetSelection($s);
	}
	
	public function CheckItemProperty(TNOrmObject $object, TOrmPropertyMeta $property){
		if ($property->Name() == 'Attributes' && !$object->ClassName)
			return false;
		
		if ($property->Name() == 'ArticleClass' && $object->BaseClass != TMSType::BASE_NOMENCLATURE)
			return false;
		
		if ($property->Name() == 'AgregationAttribute'){
			$selection = array(''=>'none');
			foreach ($object->AttrMeta as $a)
				if ($a->Type == TItemPropertyType::PT_REFERENCE)
					$selection[$a->Name] = $a->Caption;
			$property->SetSelection($selection);
		}
		
		if (($property->Name() == 'OrderOptions' || $property->Name() == 'Options') && ($object->BaseClass != TMSType::BASE_GOODS) && ($object->BaseClass != TMSType::BASE_SERVICE) && ($object->BaseClass != TMSType::BASE_NOMENCLATURE))
			return false;
		return true;
	}
		
	public function FormPhpName($name){
		$php = trim($name);
		if (!preg_match('/[a-zA-Z][a-zA-Z0-9\s]*/', preg_replace('/[^\s\w]+/','',$php))){
			if ($this->Transliter){
				$php = preg_replace('/[^\w_]+/', '', $this->Transliter->Transliterate($php));
			}
		}
		$php = preg_replace_callback('/[^\w]+(\w)/', create_function('$matches','return strtoupper($matches[1]);'), $php);
		if (!preg_match('/[a-zA-Z][a-zA-Z0-9]*/', $php)){
			$uid = new TGuidType(false,TGuidType::GUID13);
			return "Name_".preg_replace('/[^\w]+/','_',$uid->Generate());
		}
		return strtoupper(substr($php,0,1)).substr($php,1);
		
	}
	
	protected function formClassName($name){
		return 'T'.$this->FormPhpName($name);
	}
		
	protected function getTableName($class){
		switch ($class){
			case 'TMSGoods':return 'goods';break;
			case 'TMSService':return 'service';break;
			case 'TMSNomenclature':return 'nomenclature';break;
			case 'TMSUser':return 'user';break;
		}
		return strtolower(preg_replace('/([a-z])([A-Z])/','$1_$2',substr($class, 1)));
	}
	
	protected function getFieldName($property){
		return strtolower(preg_replace('/([a-z])([A-Z])/','$1_$2',$property));
	}
	
	protected function formRefProperties(TMSType $type, array &$mtm = array()){
		$result = "";
		foreach ($type->AttrMeta as $nm => $meta)
		if ($meta->Type == TItemPropertyType::PT_REFERENCE){
			$keys = $this->Adapter->Keys($meta->RefClass);
			foreach ($keys as $keyprop) {
				$result .= "\n * @property ".$meta->RefClass." $".$nm." {@orm.reference ".$keyprop->Name()."(".$this->getTableName($type->ClassName).".".$this->getFieldName($nm).")}";
				break;
			}
		} else if ($meta->Type == TItemPropertyType::PT_COLLECTION){
			$result .= "\n * @property ".$meta->RefClass." $".$nm." {@orm.collection ".$meta->BackReference."(".$this->getTableName($type->ParentClass).".".$this->idField($type->ParentClass).")".((isset($meta->SortBy) && $meta->SortBy)?(" -s ".$meta->SortBy."(asc)"):"")."}";
		} else if ($meta->Type == TItemPropertyType::PT_SET && $meta->RefClass){
			$mtm_c = $this->_mtm_cn($type->ClassName,$nm);
			$mtm[] = array($mtm_c,$meta->RefClass,$nm);
			$result .= "\n * @property ".$meta->RefClass." $".$nm." {@orm.collection ".$mtm_c.".Detail Master(".$this->getTableName($type->ParentClass).".".$this->idField($type->ParentClass).")".((isset($meta->SortBy) && $meta->SortBy)?(" -s ".$meta->SortBy."(asc)"):"")."}";
		}
		return $result;
	}
	
	protected function propertyTypeToPhp($type){
		switch ($type){
			case TItemPropertyType::PT_MULTILINE:
			case TItemPropertyType::PT_HTML:
			case TItemPropertyType::PT_PASSWORD:
			case TItemPropertyType::PT_STRING:return "string";break;
			case TItemPropertyType::PT_INT:return "int";break;
			case TItemPropertyType::PT_DEC:return "float";break;
			case TItemPropertyType::PT_DATE:return "TDate";break;
			case TItemPropertyType::PT_FILE:return "TFile";break;
			case TItemPropertyType::PT_IMAGE:return "TImage";break;
			case TItemPropertyType::PT_BOOL:return "bool";break;
			default:return "string";break;
		}
	}
	
	protected function propertyTypeToDataType($type){
		switch ($type){
			case TItemPropertyType::PT_MULTILINE:return TDataType::TEXT;break;
			case TItemPropertyType::PT_HTML:return TDataType::HTML;break;
			case TItemPropertyType::PT_PASSWORD:
			case TItemPropertyType::PT_STRING:return TDataType::STRING;break;
			case TItemPropertyType::PT_INT:return TDataType::INTEGER;break;
			case TItemPropertyType::PT_DEC:return TDataType::DECIMAL;break;
			case TItemPropertyType::PT_DATE:return TDataType::DATETIME;break;
			case TItemPropertyType::PT_FILE:return TDataType::FILELINK;break;
			case TItemPropertyType::PT_IMAGE:return TDataType::IMAGELINK;break;
			case TItemPropertyType::PT_BOOL:return TDataType::BOOLEAN;break;
			case TItemPropertyType::PT_REFERENCE:return TDataType::BIGINT;break;
		}
		return null;
	}

	protected function formProperties(TMSType $type){
		$result = "";
		foreach ($type->AttrMeta as $nm => $meta)
			switch ($meta->Type){
				case TItemPropertyType::PT_REFERENCE:{
					$result .= "
/**
 * @var ".$meta->RefClass."
 */
protected \$_orm_".$this->getFieldName($nm)."_;";
				}break;
				case TItemPropertyType::PT_SET:
				case TItemPropertyType::PT_COLLECTION:{
if ($meta->RefClass)					
					$result .= "
/**
 * @var ".$meta->RefClass."[]
 */
protected \$_orm_".$this->getFieldName($nm)."_;";
				}break;
				default:{
					$result .= "
/**
 * @var ".$this->propertyTypeToPhp($meta->Type)."
 * @orm.scalar ".$this->getTableName($type->ClassName).".".$this->getFieldName($nm)."
 */
public \$".$nm.";";
				}break;
			}
		return $result;
	}
	
	private function _type_def_gen(TMSType $type){
		$table_name = $this->getTableName($type->ClassName);
		
		$parent = ' extends '.$type->ParentClass;
		$mtm = array();
		$res =
"<?php
/**
 * @orm.table $table_name(id)
 * @orm.key Id".$this->formRefProperties($type,$mtm)."
 * @orm.identity implicit
 */				
class ".$type->ClassName.$parent." {
".(($type->BaseClass == TMSType::BASE_NONE)?"/**
 * @var int
 * @orm.scalar $table_name.id
 */	
	public \$Id;":"")."
".$this->formProperties($type)."			
}";
		
		if (!empty($mtm)){			
			foreach ($mtm as $c){
				$res .= "
/**
 * @orm.table ".$this->getTableName($c[0])."(master,detail)
 * @orm.key Master
 * @orm.key Detail
 * @orm.identity explicit
 * @property ".$type->ClassName." \$Master {@orm.reference Id(master)} 
 * @property ".$c[1]." \$Detail {@orm.reference Id(detail) greedy} 
 */				
class ".$c[0]." extends TNOrmObject {
	protected \$_orm_master_;
	protected \$_orm_detail_;		
}
				
";
			}
		}
		return $res;
	}
	
	protected function beforeSave(TNOrmObject $object){
		if (!$object->ClassName)
			$object->ClassName = $this->formClassName($object->Name);	
		
		if (!$object->BaseClass)
			$object->BaseClass = TMSType::BASE_NONE;

		if (!$object->Meta)
			$object->Meta = json_encode(array());
		
		if (!$object->Options)
			$object->Options = json_encode(array());
		
		$object->ClassDefinition = $this->_type_def_gen($object);

		return $object;
	}
	
	protected function idField($class){
		$id_field = 'id';
		switch ($class){
			case 'TMSGoods':
			case 'TMSService':$id_field = 'artid';break;
			case 'TMSNomenclature':$id_field = 'nom_id';break;
			case 'TMSUser':$id_field = 'uid';break;
		}
		return $id_field;
	}
	
	protected function getFldSize($type){
		switch ($type){
			case TItemPropertyType::PT_PASSWORD:
			case TItemPropertyType::PT_STRING:return 200;break;
			case TItemPropertyType::PT_INT:return 10;break;
			case TItemPropertyType::PT_DEC:return 18;break;
			case TItemPropertyType::PT_FILE:
			case TItemPropertyType::PT_IMAGE:return 500;break;
			case TItemPropertyType::PT_REFERENCE:return 20;break;
		}		
		return null;
	}
	
	private function _form_definition(TMSType $type,$nm,$meta,&$flds,&$ind,&$fks){
		if ($dt = $this->propertyTypeToDataType($meta->Type)){
			$dt = TDataType::GetType($dt,$meta->Size?$meta->Size:$this->getFldSize($meta->Type),2);
			if ($meta->Type == TItemPropertyType::PT_REFERENCE)
				$dt->Unsigned = true;
			$fld = new TFieldDefinition($this->getFieldName($nm),$dt);
			if ($meta->Type == TItemPropertyType::PT_REFERENCE)
				$fld->Nullable = true;
			$flds[] = $fld;
			if ($meta->Indexed)
				$ind[] = new TIndexDefinition(strtolower($type->ClassName).'_'.$fld->Name, array($fld->Name));
			if ($meta->Type == TItemPropertyType::PT_REFERENCE){
				$tbls = $this->Adapter->Tables($meta->RefClass);
				if ($tbls){
					if (!$meta->Indexed)
						$ind[] = new TIndexDefinition(strtolower($type->ClassName).'_'.$fld->Name, array($fld->Name));
					$fks[] = new TForeignKeyDefinition(
						$fld->Name,
						new TTableField(
								$this->idField($meta->RefClass),
								null,
								new TTable($tbls[count($tbls)-1])
						),
						TTableReferenceType::REL_RESTRICT);
				}
			}
		}
	}
	
	private function _mtm_cn($class,$prop){
		return 'T'.substr($class,1).$prop.'Element';
	}
	
	private function _form_mtm_def(array &$mtm = array(),$item,$nm,$meta){
		$cn = $this->_mtm_cn($item->ClassName,$nm);
		$dt = TDataType::GetType(TDataType::BIGINT,$meta->Size?$meta->Size:$this->getFldSize($meta->Type),2);
		$dt->Unsigned = true;
		$mtm[$this->getTableName($cn)] = array(
				new TFieldDefinition('Master',$dt,false),
				new TFieldDefinition('Detail',$dt,false),
				new TIndexDefinition($this->getTableName($cn).'_m', array('master')),
				new TIndexDefinition($this->getTableName($cn).'_d', array('detail')),
				new TForeignKeyDefinition(
						'master',
						new TTableField(
								$this->idField($item->ClassName),
								null,
								new TTable($this->getTableName($item->ClassName))
						),
						TTableReferenceType::REL_RESTRICT),
				new TForeignKeyDefinition(
						'detail',
						new TTableField(
								$this->idField($meta->RefClass),
								null,
								new TTable($this->getTableName($meta->RefClass))
						),
						TTableReferenceType::REL_RESTRICT),
				new TIndexDefinition('pk_'.$this->getTableName($cn),array('master','detail'),true,true)
		);
	}
	
	protected function postCreateItem(TCandyItem $item,array $data = array()){
		$dt = TDataType::GetType(TDataType::BIGINT,20);
		$dt->Unsigned = true;
		$definitions = array(new TFieldDefinition('id', $dt, false, null, true, true, $item->BaseClass == TMSType::BASE_NONE));
		
		$mtm = array();
		foreach ($item->AttrMeta as $nm => $meta)
			if ($meta->Type == TItemPropertyType::PT_SET && $meta->RefClass){
				$this->_form_mtm_def($mtm,$item, $nm, $meta);
			} else
				$this->_form_definition($item->Base, $nm, $meta, $definitions, $definitions, $definitions);
		
		if ($item->BaseClass != TMSType::BASE_NONE){
			$tbls = $this->Adapter->Tables($item->ParentClass);
			$definitions[] = new TForeignKeyDefinition("id", new TTableField($this->idField($item->ParentClass),null,new TTable($tbls[0])));
		}
				
		$this->_ioc_driver_->DefineTable($this->getTableName($item->ClassName), $definitions);
		
		foreach ($mtm as $tn => $def)
			$this->_ioc_driver_->DefineTable($tn, $def);
	}
	
	protected function postEditItem(TCandyItem $item, $origin, array $data = array()){
		if ($item->Base->Meta != $origin->Meta){
			$del_fields = array_diff(array_keys($origin->AttrMeta),array_keys($item->Base->AttrMeta));
			$add_fields = array_diff(array_keys($item->Base->AttrMeta),array_keys($origin->AttrMeta));
			
			$tn = $this->getTableName($item->ClassName);
			
			$flds = array();
			foreach ($del_fields as $nm)
				if ($origin->AttrMeta[$nm]->Type != TItemPropertyType::PT_COLLECTION){
					if ($origin->AttrMeta[$nm]->Type == TItemPropertyType::PT_SET && $origin->AttrMeta[$nm]->RefClass){
						$this->Driver->UndefineTable($this->getTableName($this->_mtm_cn($origin->ClassName, $nm)));
					} else {
						$flds[] = $this->getFieldName($nm);
						if ($origin->AttrMeta[$nm]->Type == TItemPropertyType::PT_REFERENCE)
							$this->Driver->UndefineForeignKey($tn,$this->getFieldName($nm));
					}
				}
			
			$this->Driver->UndefineFields($tn, $flds);
			
			$flds = array();
			$fks = array();
			$indx = array();
			$mtm = array();
			foreach ($add_fields as $nm)
				if ($item->AttrMeta[$nm]->Type == TItemPropertyType::PT_SET && $item->AttrMeta[$nm]->RefClass){
					$this->_form_mtm_def($mtm,$item, $nm, $item->AttrMeta[$nm]);
				} else				
					$this->_form_definition($item->Base, $nm, $item->AttrMeta[$nm], $flds, $indx, $fks);

			$this->Driver->DefineFields($tn, $flds);
			
			foreach ($indx as $ind)
				$this->Driver->DefineIndex($tn, $ind);
			
			foreach ($fks as $fk)
				$this->Driver->DefineForeignKey($tn, $fk);
			
			foreach ($mtm as $tn => $def)
				$this->_ioc_driver_->DefineTable($tn, $def);			
		}
		TFileSystem::ForceDir($this->ClassDefinitionPath);
		file_put_contents($this->ClassDefinitionPath.'/'.$item->ClassName.'.inc', $item->ClassDefinition);
	}
	
	public function DeleteObject(TNOrmObject $object){
		if (parent::DeleteObject($object))
			$this->Driver->UndefineTable($this->getTableName($object->ClassName));
	}
	
	private function _agreg_path(TMSTypeItem $type, IItem $gi){
		if ($aa = $type->AgregationAttribute){
			if ($a = $gi->$aa)
				return $this->_agreg_path($this->GetItem($a->ClassName()), $a).(string)$a.'/';
		}
		return ''; 
	}
	
	private function _get_property_meta(TMSType $type, $nm, $metadata, $pref){
		switch ($nm){
			case 'Types':{
				$result = array();
				foreach ($metadata as  $nm => $meta)
					$result[$nm] = $meta->Type;
				return $result;				
			}break;
			case 'RefClasses':{
				$result = array();
				foreach ($metadata as $nm => $meta)
					if (isset($meta->RefClass))
						$result[$nm] = $meta->RefClass;
				return $result;				
			}break;
			case 'KitProperties':{
				$result = array();
				foreach ($metadata as $nm => $meta)
					if (isset($meta->IsKitOption) && $meta->IsKitOption)
						$result[] = $nm;
				return $result;
			}break;
			case 'Captions':{
				$result = array();
				foreach ($metadata as $nm => $meta)
					$result[$nm] = $meta->Caption;
				return $result;
			}break;
			case 'Selections':{
				if (!isset($this->_selection_cache_[$type->ClassName]))
					$this->_selection_cache_[$type->ClassName] = array();
				if (!isset($this->_selection_cache_[$type->ClassName][$pref])){
					$this->_selection_cache_[$type->ClassName][$pref] = array();
					foreach ($metadata as $nm => $meta){
						if ($meta->Selection){
							if (preg_match('/^\s*#(php|sql)#(.*)$/mis', $meta->Selection,$matches)){
								if (strtolower($matches[1]) == 'sql'){
									$this->_selection_cache_[$type->ClassName][$pref][$nm] = array();
									$res = $this->Adapter->Driver->Fetch($matches[2]);
									foreach ($res as $r)
										$this->_selection_cache_[$type->ClassName][$pref][$nm][$r[0]] = $r[1];
								} else
									$this->_selection_cache_[$type->ClassName][$pref][$nm] = create_function('$item',$matches[2]);
							} else
								$this->_selection_cache_[$type->ClassName][$pref][$nm] = TConvertions::ConvertToArray($meta->Selection,array("\n","="),array(false,true));
						} else if ($meta->Type == TItemPropertyType::PT_REFERENCE || ($meta->Type == TItemPropertyType::PT_SET && $meta->RefClass)){
							$this->_selection_cache_[$type->ClassName][$pref][$nm] = array();
							if ($t = $this->GetItem($meta->RefClass)){
								$list = new TCandyItemIterator($this->Adapter->Fetch($meta->RefClass),$this);
								foreach ($list as $item)
									$this->_selection_cache_[$type->ClassName][$pref][$nm][$item->ItemId()] = $this->_agreg_path($t, $item).(string)$item;
								asort($this->_selection_cache_[$type->ClassName][$pref][$nm]);
							}
						}
					}
				}
				return $this->_selection_cache_[$type->ClassName][$pref];
			}break;
		}
		return null;
	}
	
	public function GetValue($nm){
		$parts = explode('.',$nm);
		if (count($parts) > 1){
			if ($parts[1] == 'Wrappers'){
				return array($this);
			} else if (in_array($parts[0], array('TMSGoods','TMSService','TMSArticle','TMSNomenclature','TMSClient','TMSUser','TMSPage'))){
				switch ($parts[1]){
					case 'Caption':{
						switch ($parts[0]){
							case 'TMSClient':return 'Client';break;
							case 'TMSGoods':return 'Goods';break;
							case 'TMSService':return 'Service';break;
							case 'TMSNomenclature':return 'Nomenclature';break;
						}
					}break;
					case 'AssumedDescendants':{
						if (!isset($this->_ad_cache_[$parts[0]])){
							$condition = null;
							switch ($parts[0]){
								case 'TMSGoods':$condition = new TFilterOption('BaseClass', TConditionType::C_EQUAL, TMSType::BASE_GOODS);break;
								case 'TMSService':$condition = new TFilterOption('BaseClass', TConditionType::C_EQUAL, TMSType::BASE_SERVICE);break;
								case 'TMSArticle':$condition = new TFilterOption('BaseClass', TConditionType::C_IN, array(TMSType::BASE_GOODS,TMSType::BASE_SERVICE));break;
								case 'TMSClient':
								case 'TMSUser':$condition = new TFilterOption('BaseClass', TConditionType::C_EQUAL, TMSType::BASE_CLIENT);break;
								case 'TMSNomenclature':$condition = new TFilterOption('BaseClass', TConditionType::C_EQUAL, TMSType::BASE_NOMENCLATURE);break;
							}
							$this->_ad_cache_[$parts[0]] = array();
			
							if ($condition){
								$types = $this->GetList(new TListOptions(array($condition)));
								foreach ($types as $t)
									$this->_ad_cache_[$parts[0]][] = $t->ClassName;
							}
					
							if ($parts[0] == 'TMSArticle'){
								$this->_ad_cache_[$parts[0]][] = 'TMSGoods';
								$this->_ad_cache_[$parts[0]][] = 'TMSService';
							}
						
							if ($parts[0] == 'TMSPage'){
								//$result[] = 'TMSProductPage';
								$this->_ad_cache_[$parts[0]][] = 'TMSNomenclaturePage';
								$this->_ad_cache_[$parts[0]][] = 'TMSArticlePage';
								$this->_ad_cache_[$parts[0]][] = 'TMSGlossaryItemPage';
							}
						}
						
						return $this->_ad_cache_[$parts[0]];
					}break;
				}
			} else if ($parts[0] == 'Forced'){
				if (!isset($this->_forced_cache_[$parts[1]])){
					$forced = $this->GetList(new TListOptions(array(new TFilterOption('BaseClass', TConditionType::C_EQUAL, $parts[1]),new TFilterOption('IsForced', TConditionType::C_EQUAL, true))));
					$this->_forced_cache_[$parts[1]] = false;
					foreach ($forced as $fc){
						$this->_forced_cache_[$parts[1]] = $fc->ClassName;
						break;
					}
				}
				return isset($this->_forced_cache_[$parts[1]])?$this->_forced_cache_[$parts[1]]:null;
			} else if ($parts[0] != 'TMSType'){ 
				if ($type = $this->GetItem($parts[0])){
					if ($result = $this->_get_property_meta($type->Base,$parts[1], $type->Base->AttrMeta, "attr"))
						return $result;
					
					switch ($parts[1]){ 
						case 'Caption':return $type->Name;break;
						case 'Forced':return $type->ArticleClass;break;
						case 'AssumedDescendants':return array();break;
						case 'Order':
							if (isset($parts[2]))
								if ($result = $this->_get_property_meta($type->Base,$parts[2], $type->Base->OrderOptions, "opt"))
									return $result;
						break;
					}
				}
			}
		}
		return null;
	}
	
	public function SetValue($nm,$value){return false;}
	public function SetValues(array $values){return false;}
	public function ListValues(){return array();}
	
	public function ObjectId($object){
		if ($object instanceof $this->ClassName)
			return parent::ObjectId($object);
		return $object->Id;
	}
	
	private function _classes(){
		$classes = $this->GetList();
		$class_selection = array(''=>'none',
				'TMSGoods'=>'Goods',
				'TMSService'=>'Service',
				'TMSUser'=>'User');
		
		foreach ($classes as $c)
			$class_selection[$c->ClassName] = $c->Name;
		return $class_selection;		
	}
	
	public function AttributeProperties(){
		if (empty($this->_attr_properties_)){
			$this->_attr_properties_ = array(
				'Caption' => new TOrmPropertyMeta('Caption', 'Caption', TItemPropertyType::PT_STRING),
				'Type' => new TOrmPropertyMeta('Type', 'Type', TItemPropertyType::PT_STRING,
						array(
								TItemPropertyType::PT_STRING => "String",
								TItemPropertyType::PT_INT => "Integer",
								TItemPropertyType::PT_DEC => "Decimal",
								TItemPropertyType::PT_MULTILINE => "Simple text",
								TItemPropertyType::PT_HTML => "Hypertext",
								TItemPropertyType::PT_DATE => "Date",
								TItemPropertyType::PT_FILE => "File",
								TItemPropertyType::PT_IMAGE => "Image",
								TItemPropertyType::PT_BOOL => "Boolean",
								TItemPropertyType::PT_REFERENCE => "Reference",
								TItemPropertyType::PT_COLLECTION => "Collection",
								TItemPropertyType::PT_SET => "Set",
								"url"=>"URL"
						)),
				'Size' => new TOrmPropertyMeta('Size', 'Size', TItemPropertyType::PT_INT),
				'IsKitOption' => new TOrmPropertyMeta('IsKitOption', 'Is kit option', TItemPropertyType::PT_BOOL),
				'Selection' => new TOrmPropertyMeta('Selection', 'Selection list', TItemPropertyType::PT_MULTILINE),
				'RefClass' => new TOrmPropertyMeta('RefClass', 'Referenced class', TItemPropertyType::PT_STRING,$this->_classes()),
				'BackReference' => new TOrmPropertyMeta('BackReference', 'Back reference attribute', TItemPropertyType::PT_STRING),
				'SortBy' => new TOrmPropertyMeta('SortBy', 'Sort collection by', TItemPropertyType::PT_STRING),
				'Indexed' => new TOrmPropertyMeta('Indexed', 'Is indexed', TItemPropertyType::PT_BOOL),
				'Required' => new TOrmPropertyMeta('Required', 'Is required', TItemPropertyType::PT_BOOL)
			);
		}
		return $this->_attr_properties_;		
	}
	
	public function CheckAttributeProperty(TMSTypeAttributeItem $attribute, IPropertyMeta $p){
		if ($p->Name() == 'RefClass' || $p->Name() == 'Indexed' || $p->Name() == 'Size'){
			if ($attribute->Type)
				$p->ReadOnly = true;
		}
		
		if (($p->Name() == 'Type') && $attribute->Type){
			$selection = array();
			switch ($attribute->Type){
				case "url":
				case TItemPropertyType::PT_STRING:$selection = array(TItemPropertyType::PT_STRING=>'String','url'=>'URL');break;
				case TItemPropertyType::PT_INT:$selection = array(TItemPropertyType::PT_INT=>'Integer');$p->ReadOnly = true;break;
				case TItemPropertyType::PT_DEC:$selection = array(TItemPropertyType::PT_DEC=>'Decimal');$p->ReadOnly = true;break;
				case TItemPropertyType::PT_MULTILINE:$selection = array(TItemPropertyType::PT_MULTILINE=>'Simple text',TItemPropertyType::PT_HTML=>'Hypertext');break;
				case TItemPropertyType::PT_HTML:$selection = array(TItemPropertyType::PT_MULTILINE=>'Simple text',TItemPropertyType::PT_HTML=>'Hypertext');break;
				case TItemPropertyType::PT_DATE:$selection = array(TItemPropertyType::PT_DATE=>'Дата');$p->ReadOnly = true;break;
				case TItemPropertyType::PT_FILE:$selection = array(TItemPropertyType::PT_FILE=>'File',TItemPropertyType::PT_IMAGE=>'Image');break;
				case TItemPropertyType::PT_IMAGE:$selection = array(TItemPropertyType::PT_FILE=>'File',TItemPropertyType::PT_IMAGE=>'Image');break;
				case TItemPropertyType::PT_BOOL:$selection = array(TItemPropertyType::PT_BOOL=>'Boolean');$p->ReadOnly = true;break;
				case TItemPropertyType::PT_REFERENCE:$selection = array(TItemPropertyType::PT_REFERENCE=>'Reference');$p->ReadOnly = true;break;
				case TItemPropertyType::PT_COLLECTION:$selection = array(TItemPropertyType::PT_COLLECTION=>'Collection');$p->ReadOnly = true;break;
				case TItemPropertyType::PT_SET:$selection = array(TItemPropertyType::PT_SET=>'Set');$p->ReadOnly = true;break;
			}
			$p->SetSelection($selection);
		}
		return true;		
	}
	
	public function OptionProperties(){
		if (empty($this->_opt_properties_)){
			$this->_opt_properties_ = array(
				'Caption' => new TOrmPropertyMeta('Caption', 'Caption', TItemPropertyType::PT_STRING),
				'Type' => new TOrmPropertyMeta('Type', 'Type', TItemPropertyType::PT_STRING,
						array(
								TItemPropertyType::PT_STRING => "String",
								"url" => "URL",
								TItemPropertyType::PT_INT => "Integer",
								TItemPropertyType::PT_DEC => "Decimal",
								TItemPropertyType::PT_DATE => "Date",
								TItemPropertyType::PT_BOOL => "Boolean",
								TItemPropertyTYpe::PT_REFERENCE => "Reference",
								TItemPropertyType::PT_SET => "Set"
						)),
				'RefClass' => new TOrmPropertyMeta('RefClass', 'Referenced class', TItemPropertyType::PT_STRING,$this->_classes()),
				'IsKitOption' => new TOrmPropertyMeta('IsKitOption', 'Is kit option', TItemPropertyType::PT_BOOL),
				'Selection' => new TOrmPropertyMeta('Selection', 'Selection list', TItemPropertyType::PT_MULTILINE)
			);
		}
		return $this->_opt_properties_;		
	}
	
	/**
	 * @param object $object
	 * @return TCandyItem
	 */
	public function WrapObject($object, array &$caller_stack = array()){
		if ($object instanceof TMSType)
			return new TMSTypeItem($object, $this);

		if ($type = $this->GetItem(get_class($object)))
			return new TCandyItem($object, $this);
		return parent::WrapObject($object,$caller_stack);
	}	
}